using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using UCS.XNA.CombatBiplanes.Synchronization;
using UCS.XNA.Utils;

namespace UCS.XNA.CombatBiplanes
{
    public class CombatBiplanes : Microsoft.Xna.Framework.Game, IActionExecutor
    {
        #region Constants

        private const string GAME_TITLE = "Combat Biplanes";
        private const string CONNECT_FAILED_MESSAGE = "Nao foi possivel se conectar com o servidor: {0}";
        private const string WIN_MESSAGE = "{0} venceu!";
        private const string EXIT_MESSAGE = "Pressione ESC para sair.";
        private const int MAXIMUM_PLAYERS = 4;

        #endregion

        #region Private Members

        private GraphicsDeviceManager _graphics;
        private SpriteBatch _spriteBatch;
        private RefreshableCollection<Rocket> _rocketList = new RefreshableCollection<Rocket>();
        private CollisionDetector _collisionDetector;
        private CombatBiplanesMenu _menu;
        private Player _currentPlayer;
        private MessageWindow _messageWindow;
        private bool _isPaused = false;
        private bool _mustStopGame = false;
        private bool _isInGame = false;
        private Background _background;
        private Random _random = new Random();
        private Dictionary<Player, Airplane> _playersAirplanes = new Dictionary<Player, Airplane>();
        private Player _winner = null;

        private Dictionary<Player, Airplane> _toAddAirplanes = new Dictionary<Player, Airplane>();
        private Dictionary<Player, Airplane> _toRemoveAirplanes = new Dictionary<Player, Airplane>();

        #endregion

        #region Attributes and Properties

        public int ID
        {
            get { return 0; }
            set { }
        }

        #endregion

        #region Constructors

        public CombatBiplanes()
        {
            this._graphics = new GraphicsDeviceManager(this);
            this.Content.RootDirectory = "Content";

            //this.SetFullScreen();

            this.IsMouseVisible = true;
            this.Window.Title = GAME_TITLE;
        }

        #endregion

        #region LoadUnloadContent

        protected override void LoadContent()
        {
            this._spriteBatch = new SpriteBatch(this.GraphicsDevice);
        }

        protected override void UnloadContent()
        {
        }

        #endregion

        #region Initialize

        protected override void Initialize()
        {
            base.Initialize();

            GameResources.Content = this.Content;
            GameResources.Graphics = this._graphics;
            GameResources.SpriteBatch = this._spriteBatch;

            this.InitializeContent();
            this.InitializeMenu();
            this.InitializeMessageWindow();
            this.InitializeBackground();
        }

        private void InitializeMenu()
        {
            this._menu = new CombatBiplanesMenu(new Rectangle(0, 0, this._graphics.PreferredBackBufferWidth, this._graphics.PreferredBackBufferHeight), this._graphics.GraphicsDevice, this._spriteBatch, MAXIMUM_PLAYERS);
            this._menu.ExitClicked += (sender, e) => this.Exit();
            this._menu.StartClicked += (sender, e) =>
                {
                    GameSynchronizer.Instance.SendActionToEveryone(ActionType.Start, this, null);
                };
            this._menu.ConnectClicked += (sender, e) =>
                {
                    //GameSynchronizer.Instance.Converter = new CommandConverter(this);

                    GameSynchronizer.Instance.Converter = new CommandConverter(new List<Airplane>(), this._rocketList, this);

                    var synchronizer = GameSynchronizer.Instance as ClientGameSynchronizer;

                    try
                    {
                        synchronizer.Connect(e.Value);

                        this._menu.ShowServerMenuAsClient();
                    }
                    catch (EstabilishConnectionFailedException ex)
                    {
                        this._menu.ShowMessage(string.Format(CONNECT_FAILED_MESSAGE, ex.Message));
                    }
                };
            this._menu.CancelClicked += (sender, e) =>
                {
                    GameSynchronizer.Instance.Stop();
                    GameSynchronizer.DisposeInstance();
                };
            this._menu.ServerClicked += (sender, e) =>
                {
                    GameSynchronizer.InitializeAsServer(this, MAXIMUM_PLAYERS, new CommandConverter(this));
                    GameSynchronizer.Instance.SocketError += (gsender, ge) =>
                    {
                        this.ShowMessage(ge.Value);
                        GameSynchronizer.DisposeInstance();
                        this._mustStopGame = true;
                    };
                    GameSynchronizer.Instance.Start();
                };
            this._menu.ClientClicked += (sender, e) =>
                {
                    GameSynchronizer.InitializeAsClient();
                    GameSynchronizer.Instance.SocketError += (gsender, ge) =>
                    {
                        this.ShowMessage(ge.Value);
                        GameSynchronizer.DisposeInstance();
                        this._mustStopGame = true;
                    };
                };
        }

        private void InitializeContent()
        {
            SpriteFont menuItemFont = Content.Load<SpriteFont>("MenuItemFont");
            Texture2D menuItemBorderTexture = Content.Load<Texture2D>("BorderTexture");
            Texture2D menuItemCornerTexture = Content.Load<Texture2D>("CornerTexture");
            SpriteFont gameTitleFont = Content.Load<SpriteFont>("GameTitleFont");
            SpriteFont menuTitleFont = Content.Load<SpriteFont>("MenuTitleFont");
            SpriteFont messageWindowFont = Content.Load<SpriteFont>("MessageWindowFont");
            Texture2D airplaneTexture1 = Content.Load<Texture2D>("Su-37K");
            Texture2D airplaneTexture2 = Content.Load<Texture2D>("F-15F");
            Texture2D airplaneTexture3 = Content.Load<Texture2D>("FB-23A");
            Texture2D airplaneTexture4 = Content.Load<Texture2D>("MiG-51A");
            Texture2D rocketTexture = Content.Load<Texture2D>("Rocket");
            SpriteFont playersLifeFont = Content.Load<SpriteFont>("PlayersLifeFont");
            Texture2D gameBackground = Content.Load<Texture2D>("CombatBiplanesBackground");
            Texture2D explosionTexture = Content.Load<Texture2D>("Explosion");

            ContentManager.Load(menuItemFont, menuItemBorderTexture, menuItemCornerTexture,
                                gameTitleFont, menuTitleFont, messageWindowFont,
                                airplaneTexture1, airplaneTexture2, airplaneTexture3, airplaneTexture4,
                                rocketTexture, playersLifeFont, gameBackground, explosionTexture);
        }

        private void InitializeAirplanes()
        {
            foreach (Player player in this._playersAirplanes.Keys)
            {
                int x = this._random.Next(this._graphics.PreferredBackBufferWidth);
                int y = this._random.Next(this._graphics.PreferredBackBufferHeight);

                GameSynchronizer.Instance.SendActionToEveryone(ActionType.Init, this, new int[3] { player.Number, x, y });
            }
        }

        private void InitializeCollisionDetector()
        {
            this._collisionDetector = new CollisionDetector(new List<Airplane>(), this._rocketList, new Rectangle(0, 0, this._graphics.PreferredBackBufferWidth, this._graphics.PreferredBackBufferHeight));

            this._collisionDetector.AirplaneCollided += (sender, e) =>
                {
                    if (!e.Airplane1.Exploding && !e.Airplane2.Exploding)
                    {
                        Player player1 = this._playersAirplanes.Keys.First(p => this._playersAirplanes[p] == e.Airplane1);
                        GameSynchronizer.Instance.SendActionToEveryone(ActionType.Life, this, new int[] { player1.Number });

                        e.Airplane1.Explode();

                        Player player2 = this._playersAirplanes.Keys.First(p => this._playersAirplanes[p] == e.Airplane2);
                        GameSynchronizer.Instance.SendActionToEveryone(ActionType.Life, this, new int[] { player2.Number });

                        e.Airplane2.Explode();
                    }
                };

            this._collisionDetector.AirplaneRocketCollided += (sender, e) =>
                {
                    if (!e.Rocket.IsExploding && !e.Airplane.Exploding)
                    {
                        Player player = this._playersAirplanes.Keys.First(p => this._playersAirplanes[p] == e.Airplane);
                        GameSynchronizer.Instance.SendActionToEveryone(ActionType.Life, this, new int[] { player.Number });

                        e.Rocket.Explode();
                        e.Airplane.Explode();
                    }
                };

            this._collisionDetector.RocketScreenBoundsCollided += (sender, e) =>
                {
                    if (!e.Rocket.IsExploding)
                        e.Rocket.Explode();
                };

            foreach (Airplane airplane in this._playersAirplanes.Values)
                if (airplane != null)
                    this._collisionDetector.Airplanes.Add(airplane);
        }

        private void InitializeMessageWindow()
        {
            Rectangle rect = new Rectangle();
            rect.X = 0;
            rect.Y = 0;
            rect.Width = this._graphics.PreferredBackBufferWidth;
            rect.Height = this._graphics.PreferredBackBufferHeight;
            this._messageWindow = new MessageWindow(rect, this._spriteBatch, this._graphics.GraphicsDevice, ContentManager.Instance.MessageWindowFont);
            this._messageWindow.ButtonBorder = new Border(ContentManager.Instance.MenuItemCornerTexture, ContentManager.Instance.MenuItemBorderTexture);
            this._messageWindow.Closed += (sender, e) => this.UnpauseGame();
        }

        private void InitializeBackground()
        {
            this._background = new Background(new Rectangle(0, 0, this._graphics.PreferredBackBufferWidth, this._graphics.PreferredBackBufferHeight), ContentManager.Instance.GameBackground);
            this._background.Hide();
        }

        #endregion

        #region Finalize

        private void FinalizeRocketList()
        {
            if (this._rocketList == null)
                return;

            this._rocketList.Clear();
            this._rocketList = null;
        }

        private void FinalizeCollisionDetector()
        {
            this._collisionDetector = null;
        }

        #endregion

        #region Update

        protected override void Update(GameTime gameTime)
        {
            var mouseState = Mouse.GetState();
            var keyboardState = Keyboard.GetState();

            if (keyboardState.IsKeyDown(Keys.Escape) && this._isInGame)
                this._mustStopGame = true;

            if (this._mustStopGame)
            {
                this.StopGame();
                this._mustStopGame = false;
            }

            this._background.Update(gameTime);

            if (!this._isPaused)
            {
                this._menu.Update(gameTime, mouseState, keyboardState);
                if (GameSynchronizer.Instance != null) GameSynchronizer.Instance.Update(gameTime);

                if (this._isInGame)
                {
                    this._background.Update(gameTime);
                    if (this._collisionDetector != null) this._collisionDetector.Update(gameTime);
                    if (this._playersAirplanes != null) this.UpdateAirplanes(gameTime, keyboardState);
                    if (this._rocketList != null) this.UpdateRockets(gameTime);

                    if (this._winner == null && this._playersAirplanes.Keys.Count(p => p.Lifes > 0) == 1)
                        this._winner = this._playersAirplanes.Keys.FirstOrDefault(p => p.Lifes > 0);
                }
            }

            this._messageWindow.Update(gameTime, mouseState, keyboardState);

            foreach (var pair in this._toRemoveAirplanes)
            {
                this._playersAirplanes[pair.Key] = null;
                this._collisionDetector.Airplanes.Remove(pair.Value);
                GameSynchronizer.Instance.Converter.Airplanes.Remove(pair.Value);
            }
            this._toRemoveAirplanes.Clear();

            foreach (var pair in this._toAddAirplanes)
            {
                this._playersAirplanes[pair.Key] = pair.Value;
                this._collisionDetector.Airplanes.Add(pair.Value);
                GameSynchronizer.Instance.Converter.Airplanes.Add(pair.Value);
            }
            this._toAddAirplanes.Clear();

            base.Update(gameTime);
        }

        private void UpdateRockets(GameTime gameTime)
        {
            foreach (var rocket in this._rocketList)
                rocket.Update(gameTime);
        }

        private void UpdateAirplanes(GameTime gameTime, KeyboardState keyboardState)
        {
            foreach (var airplane in this._playersAirplanes.Values)
                if (airplane != null)
                    airplane.Update(gameTime, keyboardState);
        }

        #endregion

        #region Draw

        protected override void Draw(GameTime gameTime)
        {
            this.GraphicsDevice.Clear(Color.Gray);

            this._menu.Draw(gameTime);
            this._background.Draw(gameTime);
            if (this._rocketList != null) this.DrawRockets(gameTime);
            if (this._playersAirplanes != null && this._isInGame)
            {
                this.DrawAirplanes(gameTime);
                this.DrawPlayersLifes(gameTime);
            }
            if (this._winner != null)
                this.DrawWinner(gameTime);

            this._messageWindow.Draw(gameTime);

            base.Draw(gameTime);
        }

        private void DrawRockets(GameTime gameTime)
        {
            foreach (var rocket in this._rocketList)
                rocket.Draw(gameTime);
        }

        private void DrawAirplanes(GameTime gameTime)
        {
            foreach (var airplane in this._playersAirplanes.Values)
                if (airplane != null)
                    airplane.Draw(gameTime);
        }

        private void DrawPlayersLifes(GameTime gameTime)
        {
            this._spriteBatch.Begin();

            Vector2 position = new Vector2(20, 20);
            foreach (Player player in this._playersAirplanes.Keys.OrderBy(p => p.Number))
            {
                this._spriteBatch.DrawString(ContentManager.Instance.PlayersLifeFont, player.ToString() + ": " + player.Lifes.ToString(), position, Color.White);

                position = new Vector2(position.X, position.Y + 30);
            }

            this._spriteBatch.End();
        }

        private void DrawWinner(GameTime gameTime)
        {
            this._spriteBatch.Begin();

            string text = string.Format(WIN_MESSAGE, this._winner.ToString());
            var textSize = ContentManager.Instance.PlayersLifeFont.MeasureString(text);
            Vector2 position = new Vector2(this._graphics.PreferredBackBufferWidth / 2 - textSize.X / 2, 200);
            this._spriteBatch.DrawString(ContentManager.Instance.PlayersLifeFont, text, position, Color.Yellow);

            textSize = ContentManager.Instance.PlayersLifeFont.MeasureString(EXIT_MESSAGE);
            position = new Vector2(this._graphics.PreferredBackBufferWidth / 2 - textSize.X / 2, 300);
            this._spriteBatch.DrawString(ContentManager.Instance.PlayersLifeFont, EXIT_MESSAGE, position, Color.Yellow);

            this._spriteBatch.End();
        }

        #endregion

        #region Private Methods

        private void SetFullScreen()
        {
            this._graphics.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(
                (object sender, PreparingDeviceSettingsEventArgs e) =>
                {
                    DisplayMode displayMode = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode;
                    e.GraphicsDeviceInformation.PresentationParameters.BackBufferFormat = displayMode.Format;
                    e.GraphicsDeviceInformation.PresentationParameters.BackBufferWidth = displayMode.Width;
                    this._graphics.PreferredBackBufferWidth = displayMode.Width;
                    this._graphics.PreferredBackBufferHeight = displayMode.Height;
                    e.GraphicsDeviceInformation.PresentationParameters.BackBufferHeight = displayMode.Height;
                });

            this._graphics.IsFullScreen = true;
        }

        private void StartGame()
        {
            this._menu.Hide();

            this._rocketList = new RefreshableCollection<Rocket>();
            this.InitializeAirplanes();
            this.InitializeCollisionDetector();

            this._background.Show();
            this._isInGame = true;
        }

        private void StopGame()
        {
            this._winner = null;
            this._playersAirplanes = new Dictionary<Player, Airplane>();
            this.FinalizeRocketList();
            this.FinalizeCollisionDetector();
            this.InitializeMenu();

            if (GameSynchronizer.Instance != null)
            {
                GameSynchronizer.Instance.Stop();
                GameSynchronizer.DisposeInstance();
            }

            this._background.Hid += (sender, e) => this._isInGame = false;
            this._background.Hide();
            this._menu.Show();
        }

        private void PauseGame()
        {
            this._isPaused = true;
        }

        private void UnpauseGame()
        {
            this._isPaused = false;
        }

        private Airplane CreateAirplane(Player player, Vector2 position)
        {
            Texture2D texture;
            if ((player.Number - 1) % 4 == 0)
                texture = ContentManager.Instance.AirplaneTexture1;
            else if ((player.Number - 1) % 4 == 1)
                texture = ContentManager.Instance.AirplaneTexture2;
            else if ((player.Number - 1) % 4 == 2)
                texture = ContentManager.Instance.AirplaneTexture3;
            else
                texture = ContentManager.Instance.AirplaneTexture4;

            Airplane airplane = new Airplane(player.Number, position, texture, this._spriteBatch, ContentManager.Instance.RocketTexture, ContentManager.Instance.ExplosionTexture, player == this._currentPlayer);

            airplane.FiredRocket += (sender, rocketEventArgs) =>
            {
                this._rocketList.Add(rocketEventArgs.Rocket);

                rocketEventArgs.Rocket.Exploded += (rsender, e) =>
                {
                    this._rocketList.Remove(e.Rocket);
                };
            };

            airplane.Exploded += (sender, e) =>
            {
                GameSynchronizer.Instance.SendActionToEveryone(ActionType.Fin, this, new int[] { player.Number });

                if (player.Lifes > 0)
                {
                    int x = this._random.Next(this._graphics.PreferredBackBufferWidth);
                    int y = this._random.Next(this._graphics.PreferredBackBufferHeight);

                    GameSynchronizer.Instance.SendActionToEveryone(ActionType.Init, this, new int[] { player.Number, x, y });
                }
            };

            return airplane;
        }

        private void ShowMessage(string message)
        {
            this.PauseGame();
            this._messageWindow.Message = message;
            this._messageWindow.Show();
        }

        #endregion

        #region Public Methods

        public void Execute(ActionType actionType, int[] parameters)
        {
            if (actionType == ActionType.Start)
                this.StartGame();
            else if (actionType == ActionType.Connect)
            {
                Player player = new Player(parameters[0], 3);
                this._playersAirplanes.Add(player, null);
                this._menu.ChangePlayerMenuItemToConnected(player.Number, false);
            }
            else if (actionType == ActionType.YouAre)
            {
                this._currentPlayer = this._playersAirplanes.Keys.First(p => p.Number == parameters[0]);
                this._menu.ChangePlayerMenuItemToConnected(this._currentPlayer.Number, true);
            }
            else if (actionType == ActionType.Init)
            {
                Player player = this._playersAirplanes.Keys.First(p => p.Number == parameters[0]);
                int x = parameters[1];
                int y = parameters[2];
                Airplane airplane = this.CreateAirplane(player, new Vector2(x, y));
                this._toAddAirplanes.Add(player, airplane);
            }
            else if (actionType == ActionType.Fin)
            {
                Player player = this._playersAirplanes.Keys.First(p => p.Number == parameters[0]);

                this._toRemoveAirplanes.Add(player, this._playersAirplanes[player]);
            }
            else if (actionType == ActionType.Life)
            {
                Player player = this._playersAirplanes.Keys.First(p => p.Number == parameters[0]);

                player.LoseLife();
            }
        }

        #endregion
    }
}